﻿using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Serious
{
    public class BasicModel
    {
        public static readonly BasicModel UnitCube;
        public static readonly BasicModel UnitSphere;
        public static readonly BasicModel UnitCylinder;
        public static readonly BasicModel UnitCylinderBlack;
        public static readonly Dictionary<string, BasicModel> UnitCubesColored;
        public static readonly BasicModel UnitCubeTextured;

        public interface ActualRenderer
        {
            void Draw(GraphicsDevice device);
        }
        public class VerticesRenderer<T> : ActualRenderer where T : struct, IVertexType
        {
            PrimitiveType model_type;

            T[] vertices;
            int vertices_offset;
            int num_primitives;

            public VerticesRenderer(PrimitiveType model_type, T[] vertices, int vertices_offset, int num_primitives)
            {
                this.model_type = model_type;
                this.vertices = vertices;
                this.vertices_offset = vertices_offset;
                this.num_primitives = num_primitives;
            }

            public void Draw(GraphicsDevice device)
            {
                device.DrawUserPrimitives(model_type, vertices, vertices_offset, num_primitives);
            }
        }
        public class IndecesRenderer<T> : ActualRenderer where T : struct, IVertexType
        {
            PrimitiveType model_type;

            T[] vertices;
            int vertices_offset;
            int num_vertices;

            int[] indeces;
            int indeces_offset;
            int num_primitives;

            public IndecesRenderer(PrimitiveType model_type, T[] vertices, int vertices_offset, int num_vertices, int[] indeces, int indeces_offset, int num_primitives)
            {
                this.model_type = model_type;
                this.vertices = vertices;
                this.vertices_offset = vertices_offset;
                this.num_vertices = num_vertices;
                this.indeces = indeces;
                this.indeces_offset = indeces_offset;
                this.num_primitives = num_primitives;
            }

            public void Draw (GraphicsDevice device)
            {
                device.DrawUserIndexedPrimitives(model_type, vertices, vertices_offset, num_vertices, indeces, indeces_offset, num_primitives);
            }
        }

        public static BasicModel GenerateColoredCubeModel(Vector3 half_size, Color color)
        {
            Single hsx = half_size.X;
            Single hsy = half_size.Y;
            Single hsz = half_size.Z;

            VertexPositionColorNormal[] vpcn1 = new VertexPositionColorNormal[]{
                new VertexPositionColorNormal(new Vector3( hsx, -hsy,  hsz), color, Vector3.UnitX),
                new VertexPositionColorNormal(new Vector3( hsx,  hsy,  hsz), color, Vector3.UnitX),
                new VertexPositionColorNormal(new Vector3( hsx, -hsy, -hsz), color, Vector3.UnitX),
                new VertexPositionColorNormal(new Vector3( hsx,  hsy, -hsz), color, Vector3.UnitX),
            };
            VertexPositionColorNormal[] vpcn2 = new VertexPositionColorNormal[]{
                new VertexPositionColorNormal(new Vector3(-hsx,  hsy,  hsz), color, -Vector3.UnitX),
                new VertexPositionColorNormal(new Vector3(-hsx, -hsy,  hsz), color, -Vector3.UnitX),
                new VertexPositionColorNormal(new Vector3(-hsx,  hsy, -hsz), color, -Vector3.UnitX),
                new VertexPositionColorNormal(new Vector3(-hsx, -hsy, -hsz), color, -Vector3.UnitX),
            };
            
            VertexPositionColorNormal[] vpcn3 = new VertexPositionColorNormal[]{
                new VertexPositionColorNormal(new Vector3( hsx,  hsy,  hsz), color,  Vector3.UnitY),
                new VertexPositionColorNormal(new Vector3(-hsx,  hsy,  hsz), color,  Vector3.UnitY),
                new VertexPositionColorNormal(new Vector3( hsx,  hsy, -hsz), color,  Vector3.UnitY),
                new VertexPositionColorNormal(new Vector3(-hsx,  hsy, -hsz), color,  Vector3.UnitY),
            };
            VertexPositionColorNormal[] vpcn4 = new VertexPositionColorNormal[]{
                new VertexPositionColorNormal(new Vector3(-hsx, -hsy,  hsz), color, -Vector3.UnitY),
                new VertexPositionColorNormal(new Vector3( hsx, -hsy,  hsz), color, -Vector3.UnitY),
                new VertexPositionColorNormal(new Vector3(-hsx, -hsy, -hsz), color, -Vector3.UnitY),
                new VertexPositionColorNormal(new Vector3( hsx, -hsy, -hsz), color, -Vector3.UnitY),
            };
            
            VertexPositionColorNormal[] vpcn5 = new VertexPositionColorNormal[]{
                new VertexPositionColorNormal(new Vector3(-hsx,  hsy,  hsz), color,  Vector3.UnitZ),
                new VertexPositionColorNormal(new Vector3( hsx,  hsy,  hsz), color,  Vector3.UnitZ),
                new VertexPositionColorNormal(new Vector3(-hsx, -hsy,  hsz), color,  Vector3.UnitZ),
                new VertexPositionColorNormal(new Vector3( hsx, -hsy,  hsz), color,  Vector3.UnitZ),
            };
            VertexPositionColorNormal[] vpcn6 = new VertexPositionColorNormal[]{
                new VertexPositionColorNormal(new Vector3( hsx,  hsy, -hsz), color, -Vector3.UnitZ),
                new VertexPositionColorNormal(new Vector3(-hsx,  hsy, -hsz), color, -Vector3.UnitZ),
                new VertexPositionColorNormal(new Vector3( hsx, -hsy, -hsz), color, -Vector3.UnitZ),
                new VertexPositionColorNormal(new Vector3(-hsx, -hsy, -hsz), color, -Vector3.UnitZ),
            };

            return new BasicModel(new ActualRenderer[]{
                new VerticesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleStrip, vpcn1, 0, vpcn1.Length - 2),
                new VerticesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleStrip, vpcn2, 0, vpcn2.Length - 2),
                new VerticesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleStrip, vpcn3, 0, vpcn3.Length - 2),
                new VerticesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleStrip, vpcn4, 0, vpcn4.Length - 2),
                new VerticesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleStrip, vpcn5, 0, vpcn5.Length - 2),
                new VerticesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleStrip, vpcn6, 0, vpcn6.Length - 2),
            });
        }

        public static BasicModel GenerateTexturedCubeModel(Vector3 half_size, Rectangle3 tr)
        {
            Single hsx = half_size.X;
            Single hsy = half_size.Y;
            Single hsz = half_size.Z;

            Single x0 = tr.LowX;
            Single y0 = tr.LowY;
            Single z0 = tr.LowZ;

            Single x1 = tr.HiX;
            Single y1 = tr.HiY;
            Single z1 = tr.HiZ;

            VertexPositionNormalTexture[] vpcn1 = new VertexPositionNormalTexture[]{
                new VertexPositionNormalTexture(new Vector3( hsx, -hsy,  hsz), Vector3.UnitX, new Vector2(y0, z0)),
                new VertexPositionNormalTexture(new Vector3( hsx,  hsy,  hsz), Vector3.UnitX, new Vector2(y1, z0)),
                new VertexPositionNormalTexture(new Vector3( hsx, -hsy, -hsz), Vector3.UnitX, new Vector2(y0, z1)),
                new VertexPositionNormalTexture(new Vector3( hsx,  hsy, -hsz), Vector3.UnitX, new Vector2(y1, z1)),
            };
            VertexPositionNormalTexture[] vpcn2 = new VertexPositionNormalTexture[]{
                new VertexPositionNormalTexture(new Vector3(-hsx,  hsy,  hsz), -Vector3.UnitX, new Vector2(y0, z0)),
                new VertexPositionNormalTexture(new Vector3(-hsx, -hsy,  hsz), -Vector3.UnitX, new Vector2(y1, z0)),
                new VertexPositionNormalTexture(new Vector3(-hsx,  hsy, -hsz), -Vector3.UnitX, new Vector2(y0, z1)),
                new VertexPositionNormalTexture(new Vector3(-hsx, -hsy, -hsz), -Vector3.UnitX, new Vector2(y1, z1)),
            };

            VertexPositionNormalTexture[] vpcn3 = new VertexPositionNormalTexture[]{
                new VertexPositionNormalTexture(new Vector3( hsx,  hsy,  hsz),  Vector3.UnitY, new Vector2(x0, z0)),
                new VertexPositionNormalTexture(new Vector3(-hsx,  hsy,  hsz),  Vector3.UnitY, new Vector2(x1, z0)),
                new VertexPositionNormalTexture(new Vector3( hsx,  hsy, -hsz),  Vector3.UnitY, new Vector2(x0, z1)),
                new VertexPositionNormalTexture(new Vector3(-hsx,  hsy, -hsz),  Vector3.UnitY, new Vector2(x1, z1)),
            };
            VertexPositionNormalTexture[] vpcn4 = new VertexPositionNormalTexture[]{
                new VertexPositionNormalTexture(new Vector3(-hsx, -hsy,  hsz), -Vector3.UnitY, new Vector2(x0, z0)),
                new VertexPositionNormalTexture(new Vector3( hsx, -hsy,  hsz), -Vector3.UnitY, new Vector2(x1, z0)),
                new VertexPositionNormalTexture(new Vector3(-hsx, -hsy, -hsz), -Vector3.UnitY, new Vector2(x0, z1)),
                new VertexPositionNormalTexture(new Vector3( hsx, -hsy, -hsz), -Vector3.UnitY, new Vector2(x1, z1)),
            };

            VertexPositionNormalTexture[] vpcn5 = new VertexPositionNormalTexture[]{
                new VertexPositionNormalTexture(new Vector3(-hsx,  hsy,  hsz),  Vector3.UnitZ, new Vector2(x0, y0)),
                new VertexPositionNormalTexture(new Vector3( hsx,  hsy,  hsz),  Vector3.UnitZ, new Vector2(x1, y0)),
                new VertexPositionNormalTexture(new Vector3(-hsx, -hsy,  hsz),  Vector3.UnitZ, new Vector2(x0, y1)),
                new VertexPositionNormalTexture(new Vector3( hsx, -hsy,  hsz),  Vector3.UnitZ, new Vector2(x1, y1)),
            };
            VertexPositionNormalTexture[] vpcn6 = new VertexPositionNormalTexture[]{
                new VertexPositionNormalTexture(new Vector3( hsx,  hsy, -hsz), -Vector3.UnitZ, new Vector2(x0, y0)),
                new VertexPositionNormalTexture(new Vector3(-hsx,  hsy, -hsz), -Vector3.UnitZ, new Vector2(x1, y0)),
                new VertexPositionNormalTexture(new Vector3( hsx, -hsy, -hsz), -Vector3.UnitZ, new Vector2(x0, y1)),
                new VertexPositionNormalTexture(new Vector3(-hsx, -hsy, -hsz), -Vector3.UnitZ, new Vector2(x1, y1)),
            };

            return new BasicModel(new ActualRenderer[]{
                new VerticesRenderer<VertexPositionNormalTexture>(PrimitiveType.TriangleStrip, vpcn1, 0, vpcn1.Length - 2),
                new VerticesRenderer<VertexPositionNormalTexture>(PrimitiveType.TriangleStrip, vpcn2, 0, vpcn2.Length - 2),
                new VerticesRenderer<VertexPositionNormalTexture>(PrimitiveType.TriangleStrip, vpcn3, 0, vpcn3.Length - 2),
                new VerticesRenderer<VertexPositionNormalTexture>(PrimitiveType.TriangleStrip, vpcn4, 0, vpcn4.Length - 2),
                new VerticesRenderer<VertexPositionNormalTexture>(PrimitiveType.TriangleStrip, vpcn5, 0, vpcn5.Length - 2),
                new VerticesRenderer<VertexPositionNormalTexture>(PrimitiveType.TriangleStrip, vpcn6, 0, vpcn6.Length - 2),
            });
        }

        public static BasicModel GenerateColoredSphereModel(int N, Single radius, Color color)
        {
            // todo steve -- fix this
            // first of all, it fails on n=4
            // second of all, it should be going up down NOT side side. reverse the polarities or whatever
            // im like 99% sure that "second of all" is related to "first of all"

            List<ActualRenderer> renderers = new List<ActualRenderer>();

            int NXY = N;
            int NZ = (N / 2) - 1;
            VertexPositionColorNormal[] vertices = new VertexPositionColorNormal[(NXY * NZ) + 2];

            Func<int, int, int> flat_index = delegate(int xy, int z)
            {
                return 2 + (xy * NZ) + z;
            };

            vertices[0] = new VertexPositionColorNormal(-Vector3.UnitZ, color, -Vector3.UnitZ);
            vertices[1] = new VertexPositionColorNormal(Vector3.UnitZ, color, Vector3.UnitZ);
            for (int ixy = 0; ixy < NXY; ixy++)
            {
                Single xy = ixy;
                Single xyang = xy / (Single)N * MathHelper.TwoPi;
                Vector2 xydir = Vector2Ext.FromDirection(xyang);

                for (int iz = 0; iz < NZ; iz++)
                {
                    Single z = (Single)(-(NZ / 2) + iz);
                    Single zang = z / (Single)N * MathHelper.TwoPi;
                    Vector2 zdir = Vector2Ext.FromDirection(zang);

                    Vector3 v = new Vector3(xydir * zdir.X, zdir.Y) * radius;

                    vertices[flat_index(ixy, iz)] = new VertexPositionColorNormal(v, color, v);
                }
            }

            int[] indeces = new int[NXY * NZ * 2];
            int index = 0;
            for (int ixy = 0; ixy < NXY; ixy++)
            {
                for (int iz = 0; iz < NZ; iz++)
                {
                    indeces[index++] = flat_index((ixy + 1) % NXY, iz);
                    indeces[index++] = flat_index(ixy, iz);
                }
            }
            for (int k = 0; k < NXY; k++)
            {
                renderers.Add(new IndecesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleStrip, vertices, 0, vertices.Length, indeces, k * (NZ * 2), (NZ * 2) - 2));
            }

            int[] top_indeces = new int[NXY * 3];
            index = 0;
            for (int ixy = 0; ixy < NXY; ixy++)
            {
                top_indeces[index++] = 0;
                top_indeces[index++] = flat_index(ixy, 0);
                top_indeces[index++] = flat_index((ixy + 1) % NXY, 0);
            }
            renderers.Add(new IndecesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleList, vertices, 0, vertices.Length, top_indeces, 0, top_indeces.Length / 3));

            int[] bot_indeces = new int[NXY * 3];
            index = 0;
            for (int ixy = 0; ixy < NXY; ixy++)
            {
                bot_indeces[index++] = 1;
                bot_indeces[index++] = flat_index((ixy + 1) % NXY, NZ - 1);
                bot_indeces[index++] = flat_index(ixy, NZ - 1);

            }
            renderers.Add(new IndecesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleList, vertices, 0, vertices.Length, bot_indeces, 0, bot_indeces.Length / 3));

            return new BasicModel(renderers.ToArray());
        }

        public static BasicModel GenerateColoredCylinderModel(int N, Single radius, Single half_height, Color color)
        {
            VertexPositionColorNormal[] top_vertices = new VertexPositionColorNormal[N];
            VertexPositionColorNormal[] bot_vertices = new VertexPositionColorNormal[N];
            VertexPositionColorNormal[] wall_vertices = new VertexPositionColorNormal[N * 2];
            for (int k = 0; k < N; k++)
            {
                Single angle = ((Single)k / (Single)N) * MathHelper.TwoPi;
                Vector2 radial = Vector2Ext.FromDirection(angle) * radius;

                Vector3 vtop = new Vector3(radial, half_height);
                Vector3 vbot = vtop;
                vbot.Z *= -1.0f;

                top_vertices[k] = new VertexPositionColorNormal(vtop, color, Vector3.UnitZ);
                bot_vertices[k] = new VertexPositionColorNormal(vbot, color, -Vector3.UnitZ);

                wall_vertices[(2 * k) + 0] = new VertexPositionColorNormal(vtop, color, new Vector3(radial, 0.0f));
                wall_vertices[(2 * k) + 1] = new VertexPositionColorNormal(vbot, color, new Vector3(radial, 0.0f));
            }

            int[] top_indeces = new int[(N - 2) * 3];
            int[] bot_indeces = new int[(N - 2) * 3];
            int[] wall_indeces = new int[(N + 1) * 2];

            int i = 0;
            for (int k = 1; k < N - 1; k++)
            {
                top_indeces[i]   = 0;
                bot_indeces[i++] = 0;

                top_indeces[i]   = k + 1;
                bot_indeces[i++] = k;

                top_indeces[i]   = k;
                bot_indeces[i++] = k + 1;
            }
            i = 0;
            for (int k = 0; k < N; k++)
            {
                wall_indeces[i++] = 2 * k + 1;
                wall_indeces[i++] = 2 * k;
            }
            wall_indeces[i++] = 1;
            wall_indeces[i++] = 0;

            return new BasicModel(new ActualRenderer[] {
                new IndecesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleList,  top_vertices,  0, top_vertices.Length,  top_indeces,  0, top_indeces.Length  / 3),
                new IndecesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleList,  bot_vertices,  0, bot_vertices.Length,  bot_indeces,  0, bot_indeces.Length  / 3),
                new IndecesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleStrip, wall_vertices, 0, wall_vertices.Length, wall_indeces, 0, wall_indeces.Length - 2),
            });
        }

        /**
         * assume polygon has at least three elements
         * */
        public static BasicModel GenerateExtrudedPolygon (Vector2[] polygon, Single half_height, Color color)
        {
            int N = polygon.Length;

            VertexPositionColorNormal[] top_vertices = new VertexPositionColorNormal[N];
            VertexPositionColorNormal[] bot_vertices = new VertexPositionColorNormal[N];
            VertexPositionColorNormal[] wall_vertices = new VertexPositionColorNormal[N * 2];

            Vector2 prev_point = polygon[N - 1];
            for (int k = 0; k < N; k++)
            {
                Vector2 point = polygon[k];
                Vector2 next_point = polygon[(k + 1) % N];

                Vector3 top = new Vector3(point,  half_height);
                Vector3 bot = new Vector3(point, -half_height);
                top_vertices[k] = new VertexPositionColorNormal(top, color,  Vector3.UnitZ);
                bot_vertices[k] = new VertexPositionColorNormal(bot, color, -Vector3.UnitZ);

                // too lazy to optimize this
                Vector2 prevToCurr = (point - prev_point).Rotated(Vector2Ext.FromDirection(MathHelper.PiOver2));
                Vector2 currToNext = (next_point - point).Rotated(Vector2Ext.FromDirection(MathHelper.PiOver2));
                Vector3 normal = new Vector3(prevToCurr + currToNext, 0.0f);
                wall_vertices[(2 * k) + 0] = new VertexPositionColorNormal(top, color, normal);
                wall_vertices[(2 * k) + 1] = new VertexPositionColorNormal(bot, color, normal);

                prev_point = point;
            }

            int[] top_indeces = new int[(N - 2) * 3];
            int[] bot_indeces = new int[(N - 2) * 3];
            int[] wall_indeces = new int[(N + 1) * 2];

            int i = 0;
            for (int k = 1; k < N - 1; k++)
            {
                top_indeces[i]   = 0;
                bot_indeces[i++] = 0;

                top_indeces[i]   = k;
                bot_indeces[i++] = k + 1;

                top_indeces[i]   = k + 1;
                bot_indeces[i++] = k;
            }
            i = 0;
            for (int k = 0; k < N; k++)
            {
                wall_indeces[i++] = 2 * k;
                wall_indeces[i++] = 2 * k + 1;
            }
            wall_indeces[i++] = 0;
            wall_indeces[i++] = 1;
            

            return new BasicModel(new ActualRenderer[]{
                new IndecesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleList,  top_vertices,  0, top_vertices.Length,  top_indeces,  0, top_indeces.Length  / 3),
                new IndecesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleList,  bot_vertices,  0, bot_vertices.Length,  bot_indeces,  0, bot_indeces.Length  / 3),
                new IndecesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleStrip, wall_vertices, 0, wall_vertices.Length, wall_indeces, 0, wall_indeces.Length - 2),
            });
        }

        static BasicModel ()
        {
            UnitCube = GenerateColoredCubeModel(Vector3.One, Color.White);
            UnitCylinder = GenerateColoredCylinderModel(16, 1.0f, 1.0f, Color.White);
            UnitCylinderBlack = GenerateColoredCylinderModel(16, 1.0f, 1.0f, Color.Black);
            UnitSphere = GenerateColoredSphereModel(24, 1.0f, Color.White);

            var color_stuff = new[]{
                new { color_name = "white",   color_value = Color.White   },
                new { color_name = "red",     color_value = Color.Red     },
                new { color_name = "green",   color_value = Color.Green   },
                new { color_name = "blue",    color_value = Color.Blue    },
                new { color_name = "yellow",  color_value = Color.Yellow  },

                new { color_name = "cyan",    color_value = Color.Cyan    },
                new { color_name = "magenta", color_value = Color.Magenta },
                new { color_name = "gray",    color_value = Color.Gray    },

                new { color_name = "brown",  color_value = new Color(128, 64, 32)  },
            };
            
            UnitCubesColored = new Dictionary<string, BasicModel>();
            foreach (var pair in color_stuff)
            {
                UnitCubesColored.Add(pair.color_name, GenerateColoredCubeModel(Vector3.One, pair.color_value));
            }
            
            UnitCubeTextured = GenerateTexturedCubeModel(Vector3.One, new Rectangle3(new Vector3(0.0f, 0.0f, 0.0f), new Vector3(1.0f, 1.0f, 1.0f)));
        }

        public ActualRenderer[] renderers;

        public BasicModel(ActualRenderer[] renderers)
        {
            this.renderers = renderers;
        }

        public void Draw(GraphicsDevice device)
        {
            foreach (ActualRenderer renderer in renderers)
            {
                renderer.Draw(device);
            }
        }
    }
}
